////////////////////////////////////////////////////////////////////
// File:				Renderer.cpp
// Original Author:		Gerald Leung (GL)
// Creation Date:		Tue May 06 22:21:00 PST 2008
// Spec:				http://www.csua.berkeley.edu/~gerald/
// Purpose:				CRenderer interface using GLUT
////////////////////////////////////////////////////////////////////
// Author:				Gerald Leung
// Creation Date:		Friday, February 05, 2010 7:40:26 PM
////////////////////////////////////////////////////////////////////
// Implementation-dependent includes
#include	"Windows.h"
#include	<iostream>
#include	<string>
#include	<sstream>
#include	<hash_map>

#include	"FreeImage.h"
//#include	"trig.h"
// Interface includes
#include	"Renderer.h"

using	namespace				std;

//extern	void	generateTrigTable		();
		CRenderer::CRenderer	()
{
	// GNDN function(Goes Nowhere Does Nothing)
	cout	<< "CRenderer() constructor with no args should never be called.";
	cout	<< "  Programmer screwed something up." << endl;
}

		CRenderer::~CRenderer	()
{
	// Cleanup
	delete	pBatch;
	FreeImage_DeInitialise		();
}

		CRenderer::CRenderer	(int argc, char* argv[])
{
	FreeImage_Initialise		();
	cout	<< "FreeImage " << FreeImage_GetVersion() << endl;
	cout	<< FreeImage_GetCopyrightMessage() << endl << endl;
	// process command line args
	// sArgs.resize		(nArgc);
	for (int i = 1; i < argc; i++)
	{
		// sArgs[i] = argv[i+1];
		string			s(argv[i]);
		strArgs			+= " " + s;
	}
	PrintArgs			();

	// GLUT
	glutInit			(&argc, argv);
	glutInitDisplayMode	(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);

	//The size and position of the window
	width				= 1024;
	height				= 768;

	// Windowed
	glutInitWindowSize	(width, height);
	glutInitWindowPosition(100,100);
	glutCreateWindow	(argv[0]);

	// Fullscreen
	//glutGameModeString	("1024x768:32@75");
	//glutEnterGameMode	();

	// Scene initialization (put this in constructor)
	// Set up the projection matrix stack
			glViewport			(0, 0, width, height);
			glClearColor		(0.4, 0.4, 0.4, 1.0);


	// Register Handlers
	// Displays
extern	void	Display			();
extern	void	Reshape			(int, int);
			glutDisplayFunc		(Display);
			glutReshapeFunc		(Reshape);

	// Inputs
extern	void	Motion			(int, int);
			glutMotionFunc		(Motion);

extern	void	Mouse			(int, int, int, int);
			glutMouseFunc		(Mouse);

extern	void	InputKBSpecial	(int, int, int);
extern	void	InputKB			(unsigned char, int, int);
			glutKeyboardFunc	(InputKB);
			glutSpecialFunc		(InputKBSpecial);

	// Timer
extern	void	Idle			();
			glutIdleFunc		(Idle);

	// Load Scene Data
	shademodel			= GL_SMOOTH;
	glShadeModel		(shademodel);
	//glShadeModel		(GL_SMOOTH);
	glPolygonMode		(GL_FRONT, GL_FILL);
	// glPolygonMode	(GL_BACK, GL_LINE);
	//glCullFace			(GL_BACK);
	// Don't enable this unless object has normals?
	glEnable			(GL_LIGHTING);
	//glClearDepth		(1.0f);
	glEnable			(GL_DEPTH_TEST);
	glEnable			(GL_NORMALIZE);
	
	// Data Instantiation
	// Setup Batch
	pBatch				= new Batch();
	pBatch->setNumWorkUnits(147);
	pBatch->setNumScheduled(147);
	// Set BVC to our batch
	bvc.setCurrentBatch	(pBatch);

	// Reshape				(width, height);


				// Hacked lighting code (i.e. REALLY manual lighting >.>)
				glLightModeli		(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
	GLfloat		global_ambient[]	= {0.1f,0.1f,0.1f};
				glLightModelfv		(GL_LIGHT_MODEL_AMBIENT, global_ambient);
	GLfloat		ambient[]			= {0.1f,0.1f,0.1f};
	GLfloat		diffuse[]			= {0.6f,0.5f,0.5f};
	GLfloat		specular[]			= {0.0, 0.0, 0.0, 1.0};
	GLfloat		pos[]				= {-3, 0, 2, 1};
				glLightfv			(GL_LIGHT1, GL_AMBIENT, ambient);
				glLightfv			(GL_LIGHT1, GL_DIFFUSE, diffuse);
				glLightfv			(GL_LIGHT1, GL_SPECULAR, specular);
				glLightfv			(GL_LIGHT1, GL_POSITION, pos);
				glEnable			(GL_LIGHT1);

	lastx				= 0;
	lasty				= 0;
	Buttons[0]			= 0;
	Buttons[1]			= 0;
	Buttons[2]			= 0;
	zoom				= 0.0;
	rotx				= 0.0;
	roty				= 0.0;
	tx					= 0.0;
	ty					= 0.0;
}

Batch batch;
stdext::hash_map<int, WorkUnit*> work;

void update_count(WorkUnit *wu, int delta) {
	switch (wu->status) {
	case 0:
		batch.setNumUnscheduled(batch.getNumUnscheduled() + delta);
		break;
	case 1:
		batch.setNumCompleted(batch.getNumCompleted() + delta);
		break;
	case 2:
		batch.setNumFailed(batch.getNumFailed() + delta);
		break;
	case 3:
		batch.setNumScheduled(batch.getNumScheduled() + delta);
		break;
	}
}

void pull_data() {
	// pull 16 records
	for(int i = 0; i < 1; i++) {
		HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
		if (WaitForSingleObject(h, 0) != WAIT_OBJECT_0) return;
		int type;
		cin >> type;
		
		switch (type) {
		case 0:
			int id;
			WorkUnit *wu;
			cin >> id;
			stdext::hash_map<int, WorkUnit*>::iterator it = work.find(id);
			if(it == work.end()) {
				wu = new WorkUnit();
				work[id] = wu;
			} else {
				wu = it->second;
				update_count(wu, -1);
			}
			cin >> wu->batch;
			cin >> wu->status;
			update_count(wu, 1);
		}
	}
}


void	CRenderer::Idle			()
{
	static int i = 0;
	i++;

	if (i >= 5) {
		pull_data();
		i = 0;
	}
	// Every 16ms, do:
	glutPostRedisplay	();
}

void	CRenderer::Display		()
{
	glClear				(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	glMatrixMode		(GL_MODELVIEW);
	glLoadIdentity		();

	//gluLookAt			(0.0, 0.0, 15.0,
	//					0.0, 0.0, -1.0,
	//					0.0, 1.0, 0.0);

	gluLookAt			(float(bvc._nWidth)*2.0f,
						float(bvc._nDepth) *2.0f,
						float(bvc._nWidth) *2.0f,
						float(bvc._nWidth) *0.5f,
						float(bvc._nDepth) *0.5f,
						float(bvc._nWidth) *0.5f,
						0.0, 1.0, 0.0);

	// Apply Transforms
	glScalef			(bvc._fInvScale, bvc._fInvScale, bvc._fInvScale);
	glTranslatef		(0.0f, 0.0f, -zoom/bvc._fInvScale);
	glTranslatef		(tx/bvc._fInvScale, ty/bvc._fInvScale, 0.0f);
	glTranslatef		(bvc._nWidth/2.0f, bvc._nHeight/2.0f, bvc._nDepth/2.0f);
	glRotatef			(rotx, 1.0f, 0.0f, 0.0f);
	glRotatef			(roty, 0.0f, 1.0f, 0.0f);
	glTranslatef		(-(bvc._nWidth*0.5f), -(bvc._nHeight*0.5f), -(bvc._nDepth*0.5f));

	bvc.draw			();

	glutSwapBuffers		();
}

void	CRenderer::Motion		(int x, int y)
{
	int		diffx		= x - lastx;
	int		diffy		= y - lasty;
			lastx		= x;
			lasty		= y;

	if (Buttons[0] && Buttons[1])
	{
	}
	else if (Buttons[0])
	{
		rotx	+= (float) 0.5f * diffy;
		roty	+= (float) 0.5f * diffx;
	}
	else if (Buttons[1])
	{
		tx		+= (float) 0.05f * diffx;
		ty		-= (float) 0.05f * diffy;
	}
	glutPostRedisplay			();
}

void	CRenderer::Mouse		(int btn, int state, int x, int y)
{
	int	mod			= glutGetModifiers();
	//cout			<< "(x,y) = (" << x << "," << y << ")" << endl;
	lastx			= x;
	lasty			= y;
	switch(btn)
	{
	case GLUT_LEFT_BUTTON:
		Buttons[0]	= ((GLUT_DOWN==state)?1:0);
		break;
	case GLUT_MIDDLE_BUTTON:
		Buttons[1]	= ((GLUT_DOWN==state)?1:0);
		break;
	case GLUT_RIGHT_BUTTON:
		Buttons[2]	= ((GLUT_DOWN==state)?1:0);
		break;
	case GLUT_WHEEL_UP:
		cout		<< "zoom in" << endl;
		zoom		-= 0.50f;
		break;
	case GLUT_WHEEL_DOWN:
		cout		<< "zoom out" << endl;
		zoom		+= 0.50f;
		break;
	default:
		break;		
	}
	//glutPostRedisplay();
}

void	CRenderer::InputKBSpecial(int key, int x, int y)
{
	int	mod				= glutGetModifiers();
	switch(key) {
	case GLUT_KEY_UP:
		if (mod == (GLUT_ACTIVE_CTRL | GLUT_ACTIVE_SHIFT)) {
			cout << "Ctrl+Shift+Up" << endl;
		} else if (mod == GLUT_ACTIVE_CTRL) {
		 	//psScene->camCamera->forward		(3.0);
			cout << "Ctrl+Up" << endl;
		} else if (mod == GLUT_ACTIVE_SHIFT) {
			//psScene->pCurObject->ty		+= 0.05;
			cout << "Shift+Up" << endl;
		} else {
			//psScene->pCurObject->rotx		-= 1.0;
		 	//psScene->camCamera->forward		(0.3);
			cout << "Up" << endl;
		}
		break;
	case GLUT_KEY_DOWN:
		if (mod == (GLUT_ACTIVE_CTRL | GLUT_ACTIVE_SHIFT)) {
			cout << "Ctrl+Shift+Down" << endl;
		} else if (mod == GLUT_ACTIVE_CTRL) {
		 	//psScene->camCamera->forward		(-1.0);
			cout << "Ctrl+Down" << endl;
		} else if (mod == GLUT_ACTIVE_SHIFT) {
			//psScene->pCurObject->ty		-= 0.05;
			cout << "Shift+Down" << endl;
		} else {
			//psScene->pCurObject->rotx		+= 1.0;
		 	//psScene->camCamera->forward		(-0.1);
			cout << "Down" << endl;
		}
		break;
	case GLUT_KEY_LEFT:
		if (mod == (GLUT_ACTIVE_CTRL | GLUT_ACTIVE_SHIFT)) {
			cout << "Ctrl+Shift+Left" << endl;
		} else if (mod == GLUT_ACTIVE_CTRL) {
			cout << "Ctrl+Left" << endl;
		} else if (mod == GLUT_ACTIVE_SHIFT) {
			//psScene->pCurObject->tx		-= 0.05;
			cout << "Shift+Left" << endl;
		} else {
			//psScene->pCurObject->roty		-= 1.0;
		 	//psScene->camCamera->yaw			(5.0);
			cout << "Left" << endl;
		}
		break;
	case GLUT_KEY_RIGHT:
		if (mod == (GLUT_ACTIVE_CTRL | GLUT_ACTIVE_SHIFT)) {
			cout << "Ctrl+Shift+Right" << endl;
		} else if (mod == GLUT_ACTIVE_CTRL) {
			cout << "Ctrl+Right" << endl;
		} else if (mod == GLUT_ACTIVE_SHIFT) {
			//psScene->pCurObject->tx		+= 0.05;
			cout << "Shift+Right" << endl;
		} else {
			//psScene->pCurObject->roty		+= 1.0;
		 	//psScene->camCamera->yaw			(-5.0);
			cout << "Right" << endl;
		}
		break;
	default:
		cerr	<< "That key is not recognized" << endl;
		break;
	}
	// glutPostRedisplay	();
}
void	CRenderer::InputKB		(unsigned char key, int x, int y)
{
	//int					nRM;
	//float				f;
	string				sString;
	switch				(key)
	{
	case 's':
	case 'S':
		// Toggle: Flat-shading, Smooth-shading
		glGetIntegerv(GL_SHADE_MODEL, &shademodel);
		if (shademodel == GL_SMOOTH)
		{
			glShadeModel(GL_FLAT);
			//SMOOTH_SHADING = false;
			cout		<< "Shading: FLAT" << endl;
		}
		else
		{
			glShadeModel(GL_SMOOTH);
			//SMOOTH_SHADING = true;
			cout		<< "Shading: SMOOTH" << endl;
		}
		// cout			<< "s (Shading Model toggles)" << endl;
		break;
	case 'l':
	case 'L':
		// Toggle Lights
		if (glIsEnabled(GL_LIGHTING))
		{
			glDisable	(GL_LIGHTING);
			cout		<< "l (GL_LIGHTING disabled)" <<endl;
		}
		else
		{
			glEnable	(GL_LIGHTING);
			cout		<< "l (GL_LIGHTING enabled)" <<endl;
		}
		break;
 	case 'w':
	case 'W':
		// Cycle: Shaded, Wireframe + HSR, Wireframe
		cout			<< "w (Wireframe, Filled toggle)" << endl;
		break;
	case 'h':
	case 'H':
		cout			<< "h (Wireframe, Hidden Line Removal)" << endl;
		break;
	case '+':
	case '=':
		cout			<< "+" << endl;
		break;
	case '-':
	case '_':
		cout			<< "-" << endl;
		break;
	case 'q':
	case 'Q':
		cout			<< "Quitting..." << endl;
		// SglutLeaveGameMode();
		exit			(0);
		break;
	case 'o':
		cout			<< "(Change to console window to type)" << endl << "Save to image file:";
		cout			<< endl << ">> ";
		cin				>> sString;
		break;
	case 'O':
		break;
	case 'r':
		//psScene->camCamera = psScene->origCamera;
		cout			<< "resetting camera..." << endl;
		break;
	default:
		cout			<< key << " key not supported" << endl;
	}
	// have glut call the redraw function
	glutPostRedisplay			();
}
void	CRenderer::Reshape		(int w, int h)
{
	width						= w;
	height						= h;
	glViewport					(0, 0, (GLsizei)width, (GLsizei)height);
	//psScene->reshape			(width, height);
	float	aspect				= float(w)/float(h);
	glMatrixMode				(GL_PROJECTION);
	glLoadIdentity				();
	gluPerspective				(65, aspect, 0.1, 100);
}

void	CRenderer::PrintArgs	()
{
	cout	<< "options:";
	if		(strArgs == "")
		cout	<< " <none>" << endl;
	else
		cout	<< strArgs << endl;
}